// milktruck.js  -- Copyright Google 2007

// Code for Monster Milktruck demo, using Earth Plugin.

window.truck = null;

// Pull the Milktruck model from 3D Warehouse.
var MODEL_URL = 'http://sketchup.google.com/3dwarehouse/download?mid='+GLIDER+'&rtyp=zip&fn=paraglider&ctyp=paraglider';

//var MODEL_URL = 'http://sketchup.google.com/3dwarehouse/download?mid=3c9a1cac8c73c61b6284d71745f1efa9&rtyp=zip&fn=milktruck&ctyp=milktruck';

var TICK_MS = 66;

var STEER_ROLL = 7.0;
var ROLL_SPRING = 0.14;
var ROLL_DAMP = -0.06;

var STEER_TILT = 3.0;
var TILT_SPRING = 0.14;
var TILT_DAMP = -0.03;

function Truck() {
  var me = this;

  me.doTick = true;
  
  // We do all our motion relative to a local coordinate frame that is
  // anchored not too far from us.  In this frame, the x axis points
  // east, the y axis points north, and the z axis points straight up
  // towards the sky.
  //
  // We periodically change the anchor point of this frame and
  // recompute the local coordinates.
  me.localAnchorLla = [0, 0, 0];
  me.localAnchorCartesian = V3.latLonAltToCartesian(me.localAnchorLla);
  me.localFrame = M33.identity();

  // Position, in local cartesian coords.
  me.pos = [0, 0, 0];
  
  // Velocity, in local cartesian coords.
  me.vel = [0, 0, 0];

  //have I taken off once ?
  me.haveLaunched = 0;
  me.inFlight = 0;
  me.lastBeep = (new Date()).getTime();
  me.beepFrequency = 0;

  //thermals and wind
  me.wind=[0,0,0];
  me.flyingInThermal = 0;
  me.thermalUpliftSpeed = 0;

  //flight stats
  me.maxDistanceFromTakeOff = 0;
  me.maxAltitude = 0 ;

  //flightlog : stocks flight waypoints
  me.flightLog = [];
  me.gMapFlightLog = [];
  var actualDate = new Date();
  var today = actualDate.getDate() ;
  if (today < 10){ var todayString = '0'+today; }
  else { var todayString = ''+today; }
  var actualMonth = actualDate.getMonth() ;
  actualMonth = actualMonth + 1 ;
  if ( actualMonth < 10){ var monthString = '0'+actualMonth; }
  else { var monthString = ''+actualMonth; } 
  actualYear = actualDate.getFullYear()+'';
  actualYear = actualYear.substr(2,4);
  var igcDate = todayString+''+monthString+actualYear;
  me.igc  = 'AXGD001 SP24XC  SW2.70\n';
  me.igc += 'HFDTE '+ igcDate +'\n';
  me.igc += 'HOPLTPILOT: '+ PILOT_NAME +'\n';
  me.igc += 'HOGTYGLIDERTYPE: Paraglide\n';
  me.igc += 'HODTM100GPSDATUM: WGS-84\n';
  me.igc += 'HOCCLCOMPETITION CLASS: PGEPGSIM\n';
  //me.igc += 'HOSITSite: '+ SITE_NAME +'\n';
  me.StopWrittingIGCOnceForAll = false;



    var dateNow = new Date();
    var latText='N';
    var lngText='E';
    var lat = TAKEOFF_LAT ;
    var lng = TAKEOFF_LNG ;
    if (lat<0) {latText='S'; lat = - lat} 
    if (lng<0) {lngText='W'; lng = - lng}

    var lat = addExtraZeros(2, Math.floor(lat))+''+ addExtraZeros(5, Math.round( 60 * Math.round(100000 * (lat - Math.floor(lat))) / 100)); // convert decimal degrees to decimal minutes
    var lng = addExtraZeros(3, Math.floor(lng))+''+ addExtraZeros(5, Math.round( 60 * Math.round(100000 * (lng - Math.floor(lng))) / 100)); // convert decimal degrees to decimal minutes
    me.igc += 'B'+ addExtraZeros(2,dateNow.getHours()) +''+addExtraZeros(2,dateNow.getMinutes())+''+addExtraZeros(2,dateNow.getSeconds());
    me.igc += lat+''+latText;
    me.igc += lng+''+lngText;
    me.igc += 'A'+addExtraZeros(10, Math.round(ge.getGlobe().getGroundAltitude(TAKEOFF_LAT, TAKEOFF_LNG)))+'\n';


  me.oneOutOfHundred = 0;

  // Orientation matrix, transforming model-relative coords into local
  // coords.
  me.modelFrame = M33.identity();

  me.roll = 0;
  me.rollSpeed = 0;
  
  me.tilt = 0;
  me.tiltSpeed = 0;
  
  me.idleTimer = 0;
  me.fastTimer = 0;
  me.popupTimer = 0;

  me.camZoom = 1;

  ge.getOptions().setFlyToSpeed(100);  // don't filter camera motion

//__________________________
//   Get another glider via a kml
function uploadKml(gliderId){

}


function finished(){
}
google.earth.fetchKml(ge, 'http://www.paraglidingearth.com/pgepgsim/multiplayer/1-load.kml',
                               finished());

//google.earth.fetchKml(ge, 'http://www.paraglidingearth.com/pgepgsim/multiplayer/load-update.kml',
//                               uploadKml('1'));
//__________________________

  google.earth.fetchKml(ge, MODEL_URL,
                               function(obj) { me.finishInit(obj); });
}

Truck.prototype.finishInit = function(kml) {
  var me = this;

  // The model zip file is actually a kmz, containing a KmlFolder with
  // a camera KmlPlacemark (we don't care) and a model KmlPlacemark
  // (our milktruck).
  me.placemark = kml.getFeatures().getChildNodes().item(1);
  me.model = me.placemark.getGeometry();
  me.orientation = me.model.getOrientation();
  me.location = me.model.getLocation();

  me.model.setAltitudeMode(ge.ALTITUDE_ABSOLUTE);
  me.orientation.setHeading(90);
  me.model.setOrientation(me.orientation);

  ge.getFeatures().appendChild(me.placemark);

  me.balloon = ge.createHtmlStringBalloon('');
  me.balloon.setFeature(me.placemark);
  me.balloon.setMaxWidth(200);

  me.teleportTo(TAKEOFF_LAT, TAKEOFF_LNG, TAKEOFF_HEADING);

  me.lastMillis = (new Date()).getTime();

  me.lastThermalSetUpPos = [0, 0, 0];
  me.thermalCenter= new Array(THERMAL_DENSITY);
  me.thermalCenterLla= new Array(THERMAL_DENSITY);
  for (var i=0; i<THERMAL_DENSITY; i++){
    me.thermalCenter[i] = [0, 0, 0];
    me.thermalCenterLla[i] = [0, 0, 0];
  }

  var href = window.location.href;
  var pagePath = href.substring(0, href.lastIndexOf('/')) + '/';

  me.shadow = ge.createGroundOverlay('');
  me.shadow.setVisibility(false);
  me.shadow.setIcon(ge.createIcon(''));
  me.shadow.setLatLonBox(ge.createLatLonBox(''));
  me.shadow.setAltitudeMode(ge.ALTITUDE_CLAMP_TO_GROUND);
  me.shadow.getIcon().setHref(pagePath + 'img/shadowrect.png');
  me.shadow.setVisibility(true);
  ge.getFeatures().appendChild(me.shadow);

  me.takeOffAltitude = ge.getGlobe().getGroundAltitude(TAKEOFF_LAT, TAKEOFF_LNG) + WINCH_ALTITUDE;

  google.earth.addEventListener(ge, "frameend", function() { me.tick(); });

  me.cameraCut();
}

leftButtonDown = false;
hardLeftButtonDown = false;
rightButtonDown = false;
hardRightButtonDown = false;
gasButtonDown = false;
reverseButtonDown = false;
takeoffButtonDown = false;
takeoffCheatButtonDown = false;
turnHeadLeft = false ;
turnHeadRight = false ;
turnHeadBack = false ;
turnHeadDown = false ;
cameraZoomIn = false ;
cameraZoomOut = false ;
varioSoundOnOff = true;

function switchTrueFalse(stuff){
  if (stuff) {
    stuff=false
  } else {
    stuff=true
  }
  return stuff;
}

function keyDown(event) {
  if (!event) {
    event = window.event;
  }
  if (event.keyCode == 37) {  // Left.
    leftButtonDown = true;
    event.returnValue = false;
  } else if (event.keyCode == 39) {  // Right.
    rightButtonDown = true;
    event.returnValue = false;
  } else if (event.keyCode == 38) {  // Up.
    gasButtonDown = true;
    event.returnValue = false;
  } else if (event.keyCode == 40) {  // Down.
    reverseButtonDown = true;
    event.returnValue = false;
  } else if (event.keyCode == 32) {  // Run !.
    takeoffButtonDown = true;
    event.returnValue = false;
  } else if (event.keyCode == 83) {  // s.
    turnHeadLeft = true;
    event.returnValue = false;
  } else if (event.keyCode == 68) {  // d.
    turnHeadRight = true;
    event.returnValue = false;
  } else if (event.keyCode == 88) {  // x.
    turnHeadBack = true;
    event.returnValue = false;
  } else if (event.keyCode == 70) {  // f.
    turnHeadDown = true;
    event.returnValue = false;
  } else if (event.keyCode == 65) {  // a.
    cameraZoomIn = true;
    event.returnValue = false;
  } else if (event.keyCode == 90) {  // z.
    cameraZoomOut = true;
    event.returnValue = false;
  } else if (event.keyCode == 86) {  // v.
    varioSoundOnOff = switchTrueFalse(varioSoundOnOff);
    event.returnValue = false;
  } else {
    return true;
  }
  return false;
}

function keyUp(event) {
  if (!event) {
    event = window.event;
  }
  if (event.keyCode == 37) {  // Left.
    leftButtonDown = false;
    event.returnValue = false;
  } else if (event.keyCode == 39) {  // Right.
    rightButtonDown = false;
    event.returnValue = false;
  } else if (event.keyCode == 38) {  // Up.
    gasButtonDown = false;
    event.returnValue = false;
  } else if (event.keyCode == 40) {  // Down.
    reverseButtonDown = false;
    event.returnValue = false;
  } else if (event.keyCode == 32) {  // Space Bar.
    takeoffButtonDown = false;
    event.returnValue = false;
  } else if (event.keyCode == 83) {  // s.
    turnHeadLeft = false;
    event.returnValue = false;
  } else if (event.keyCode == 68) {  // d.
    turnHeadRight = false;
    event.returnValue = false;
  } else if (event.keyCode == 70) {  // f.
    turnHeadDown = false;
    event.returnValue = false;
  } else if (event.keyCode == 65) {  // a.
    cameraZoomIn = false;
    event.returnValue = false;
  } else if (event.keyCode == 90) {  // z.
    cameraZoomOut = false;
    event.returnValue = false;
  } else if (event.keyCode == 88) {  // x.
    turnHeadBack = false;
    event.returnValue = false;
  }
  return false;
}

function clamp(val, min, max) {
  if (val < min) {
    return min;
  } else if (val > max) {
    return max;
  }
  return val;
}

Truck.prototype.tick = function() {
  var me = this;

  var now = (new Date()).getTime();
  // dt is the delta-time since last tick, in seconds
  var dt = (now - me.lastMillis) / 1000.0;
  if (dt > 0.25) {
    dt = 0.25;
  }
  me.lastMillis = now;

// document.getElementById("jsInfo").innerHTML = 'thermals : ';

  var c0 = 1;
  var c1 = 0;

  var gpos = V3.add(me.localAnchorCartesian,
                    M33.transform(me.localFrame, me.pos));
  var lla = V3.cartesianToLatLonAlt(gpos);
  me.lla=lla;

  var distanceLastThermalSetUp = getDistance(lla,me.lastThermalSetUpPos);

  if (distanceLastThermalSetUp > 500){
    me.setUpThermals(lla);
    distanceLastThermalSetUp = 0
  }

 // document.getElementById("jsInfo3").innerHTML = '';

  if (me.flyingInThermal == 0 & me.lla[2] < THERMAL_TOP){
    for(var j=0; j < THERMAL_DENSITY; j++){
      var distanceToCenter = getDistance(me.lla, me.thermalCenterLla[j]);
//      document.getElementById("jsInfo3").innerHTML = '//';
//      document.getElementById("jsInfo3").innerHTML += '<br />'+j+' : '+distanceToCenter+" m";
      if (distanceToCenter  < THERMAL_RADIUS){
        me.myActualThermalLla =  me.thermalCenterLla[j];
        me.inThermal();
      }
    }
  } else {
    me.inThermal();
  }

  if (V3.length([me.pos[0], me.pos[1], 0]) > 100) {
    // Re-anchor our local coordinate frame whenever we've strayed a
    // bit away from it.  This is necessary because the earth is not
    // flat!
    me.adjustAnchor();
  }

  var dir = me.modelFrame[1];
  var up = me.modelFrame[2];

  var absSpeed = V3.length(me.vel);

  var groundAlt = ge.getGlobe().getGroundAltitude(lla[0], lla[1]);
  var airborne = (groundAlt + 0.30 < me.pos[2]);
  var steerAngle = 0;

//  var normal = estimateGroundNormal(gpos, me.localFrame);

  // Steering.
  if (leftButtonDown || rightButtonDown || hardLeftButtonDown || hardRightButtonDown) {
    var TURN_SPEED_MIN = 54.0;  // radians/sec
    var TURN_SPEED_MAX = 120.0;  // radians/sec
 
    var turnSpeed;

    var SPEED_MAX_TURN = 25.0;
    var SPEED_MIN_TURN = 120.0;
    if (hardLeftButtonDown || hardRightButtonDown) {
      turnSpeed = TURN_SPEED_MAX ;
      me.vel[2] = -8 ;
    } else {
      turnSpeed = TURN_SPEED_MIN;
    }
    if (leftButtonDown || hardLeftButtonDown) {
      steerAngle = turnSpeed * dt * Math.PI / 180.0;
    }
    if (rightButtonDown || hardRightButtonDown) {
      steerAngle = -turnSpeed * dt * Math.PI / 180.0;
    }
  } else {
    if (me.inFlight) {me.vel[2] = -1.3 ;}
  }

  // Turn.
  var newdir = V3.rotate(dir, up, steerAngle);
  me.modelFrame = M33.makeOrthonormalFrame(newdir, up);
  me.vel = V3.rotate(me.vel, up, steerAngle);
  dir = me.modelFrame[1];
  up = me.modelFrame[2];

  var forwardSpeed = 0;
  var verticalSpeed = 0;

/*//if airborn, sinkrate = 1.3 m/s
  if (airborne) {
   me.vel[2] = -1.3;
  }
*/

  forwardSpeed = V3.dot(dir, me.vel);
  verticalSpeed = V3.dot(up, me.vel);


    // Apply engine/reverse accelerations.
    var ACCEL = 2.0;
    var DECEL = 2.0;
    var MAX_SPEED = 11.6;
    var MIN_SPEED = 7;

    forwardSpeed = V3.dot(dir, me.vel);

    if (gasButtonDown & forwardSpeed < MAX_SPEED) {
      // Accelerate forwards.
      me.vel = V3.add(me.vel, V3.scale(dir, ACCEL * dt));
    } else if (reverseButtonDown & forwardSpeed > MIN_SPEED) {
      me.vel = V3.add(me.vel, V3.scale(dir, -DECEL * dt));
    }
  

  //Evaluate local wind direction
  if (me.inFlight) {
  gpos = V3.add(me.localAnchorCartesian,
                M33.transform(me.localFrame, me.pos));
  lla = V3.cartesianToLatLonAlt(gpos);     //get actual position as we need it to compute ridge effect depending on glider position
    me.windLocal(lla);
    //me.vel[2] = me.wind;
  } else {
    me.wind = [0, 0, 0];
  }


  // Move.
  var deltaPos = V3.scale(me.vel, dt);
  if (me.inFlight) {                  //if airborn, float with the wind
     airSpeed = V3.length(V3.add(me.vel, me.wind));
     deltaPos =  V3.add(deltaPos, V3.scale(me.wind, dt));
    if (me.inFlight & me.flyingInThermal==1){
      deltaPos =  V3.add(deltaPos, V3.scale([0, 0, me.thermalUpliftSpeed], dt));
    }
  }
  me.pos = V3.add(me.pos, deltaPos);
  gpos = V3.add(me.localAnchorCartesian,
                M33.transform(me.localFrame, me.pos));
  lla = V3.cartesianToLatLonAlt(gpos);


  // Don't go underground.
  groundAlt = ge.getGlobe().getGroundAltitude(lla[0], lla[1]);
  if (me.pos[2] < groundAlt) {
    me.pos[2] = groundAlt;
    me.vel = [0,0,0];
    if (me.inFlight == 1){
      me.endFlight();
    }
  }

  // Get a start for the flight to be able to end it later
  if (me.pos[2]-10 > groundAlt & me.haveLaunched==1 & me.inFlight==0) {
/*    me.popupTimer = 2.0;
    me.balloon.setContentString("Let's fly!!  :)");
    me.lastThermalSetUpPos = lla;
    me.setUpThermals(lla);          
    ge.setBalloon(me.balloon);  */
    me.inFlight =1;
    document.getElementById("takeOffButton").style.display = 'none';
    document.getElementById("pilotButtons").style.display = 'block';
  }




  // +100m height cheat
 /* if (cheatUpButtonDown){
    me.pos[2] = me.pos[2] + 100;
  } */

   //Taking off
  if (takeoffButtonDown){
    //TODO : re-orient glider to the max-slope direction + test if enough slope to launch
    me.vel = [10*dir[0],10*dir[1],-1.3];
    me.takeOffAltitude = me.pos[2];
    me.maxAltitude = me.pos[2];
    me.haveLaunched = 1 ;
  }
  
   //Take off Cheat : +10m
  if (takeoffCheatButtonDown){
		me.teleportTo(TAKEOFF_LAT, TAKEOFF_LNG,  TAKEOFF_HEADING, 10);
  }


   // Make our orientation always up.
   //me.modelFrame = M33.makeOrthonormalFrame(dir, up);


  // Propagate our state into Earth.
  gpos = V3.add(me.localAnchorCartesian,
                M33.transform(me.localFrame, me.pos));
  lla = V3.cartesianToLatLonAlt(gpos);
  me.model.getLocation().setLatLngAlt(lla[0], lla[1], lla[2]);

  var newhtr = M33.localOrientationMatrixToHeadingTiltRoll(me.modelFrame);

  if (me.oneOutOfHundred == 350){
    me.gMapFlightLog.push(new GLatLng(lla[0], lla[1]));


    if (!me.StopWrittingIGCOnceForAll && me.inFlight==1){
       var dateNow = new Date();
       var latText='N';
       var lngText='E';
       var lat = lla[0];
       var lng = lla[1];
       if (lla[0]<0) {latText='S'; lat = - lat} 
       if (lla[1]<0) {lngText='W'; lng = - lng}

       var lat = addExtraZeros(2, Math.floor(lat))+''+ addExtraZeros(5, Math.round( 60 * Math.round(100000 * (lat - Math.floor(lat))) / 100)); // convert decimal degrees to decimal minutes
       var lng = addExtraZeros(3, Math.floor(lng))+''+ addExtraZeros(5, Math.round( 60 * Math.round(100000 * (lng - Math.floor(lng))) / 100)); // convert decimal degrees to decimal minutes
       me.igc += 'B'+ addExtraZeros(2,dateNow.getHours()) +''+addExtraZeros(2,dateNow.getMinutes())+''+addExtraZeros(2,dateNow.getSeconds());
       me.igc += lat+''+latText;
       me.igc += lng+''+lngText;
       me.igc += 'A'+addExtraZeros(10, Math.round(lla[2]))+'\n';
    }

    me.oneOutOfHundred = 0;
    var distanceFromTakeOff = getDistance([me.lla[0], me.lla[1]],[TAKEOFF_LAT, TAKEOFF_LNG]);
    if (distanceFromTakeOff > me.maxDistanceFromTakeOff)   me.maxDistanceFromTakeOff = distanceFromTakeOff ;
    if (me.pos[2] > me.maxAltitude)   me.maxAltitude = me.pos[2] ;
  }

  // Compute roll and tilt according to steering.
  // TODO : tilt does not seem to work...
  var absRoll = newhtr[2];
  me.rollSpeed += steerAngle * forwardSpeed * STEER_ROLL;
  // Spring back to center, with damping.
  me.rollSpeed += (ROLL_SPRING * -me.roll + ROLL_DAMP * me.rollSpeed);
  me.roll += me.rollSpeed * dt;
  me.roll = clamp(me.roll, -90*turnSpeed/120, 90*turnSpeed/120);
  absRoll += me.roll;

  var absTilt = newhtr[1];
  me.tiltSpeed += Math.abs(steerAngle) * forwardSpeed * STEER_TILT;
  // Spring back to center, with damping.
  me.tiltSpeed += (TILT_SPRING * -me.tilt + TILT_DAMP * me.tiltSpeed);
  me.tilt += me.tiltSpeed * dt;
  me.tilt = clamp(me.tilt, -90*turnSpeed/120, 90*turnSpeed/120);
  absTilt += me.tilt;

 // absTilt =   absRoll * absRoll / 90 ;

  me.orientation.set(newhtr[0], absTilt, absRoll);

  var latLonBox = me.shadow.getLatLonBox();
  var radius = .00005;
  latLonBox.setNorth(lla[0] - radius);
  latLonBox.setSouth(lla[0] + radius);
  latLonBox.setEast(lla[1] - 4*radius);
  latLonBox.setWest(lla[1] + 4*radius);
  latLonBox.setRotation(-newhtr[0]);


 // me.tickPopups(dt);
  me.oneOutOfHundred = me.oneOutOfHundred + 1;

  var headHeading = 0;
  var headTilt = 80;
  if (turnHeadLeft) headHeading = -90;
  if (turnHeadRight) headHeading = 90;
  if (turnHeadBack) headHeading = 180;
  if (turnHeadDown) headTilt = 0;
  me.cameraFollow(dt, gpos, me.localFrame, headHeading, headTilt);

 // document.getElementById("jsInfo").innerHTML = 'ParaglidingEarth PG Sim :)';
/*  document.getElementById("jsInfo").innerHTML += '<br />flying ?  '+ me.inFlight +'';
  document.getElementById("jsInfo").innerHTML += '<br />x speed = '+ Math.round(100*me.vel[0])/100 +' m/s';
  document.getElementById("jsInfo").innerHTML += '<br />absx speed = '+ Math.round(100*absSpeed)/100 +' m/s';
  document.getElementById("jsInfo").innerHTML += '<br />y speed = '+ Math.round(100*me.vel[1])/100 +' m/s';
  document.getElementById("jsInfo").innerHTML += '<br />vertical air speed = '+Math.round(100*verticalSpeed)/100 +' m/s';
*/


  //================ INSTRUMENTS  ============================
  var Vz  = Math.round(10*deltaPos[2]/dt)/10 ;
  var Vz1px=150;
  var Vz2px=1;

  if (Vz >= 0 & Vz <= 4){
    var Vz1px =   Math.round(150-20*Vz);
    var Vz2px =   Math.round(1+20*Vz);
  }
  if (Vz > 4 & Vz < 8){
    var Vz1px =   70;
    var Vz2px =   Math.round(81-20*(Vz-4));
  }
  if (Vz < 0 & Vz >= -4) {
    var Vz1px =   150;
    var Vz2px =   Math.round(1-20*Vz);
  }
  if (Vz < -4 & Vz > -8) {
    var Vz1px =   150-20*(Vz+4);
    var Vz2px =   81;
  }
  document.getElementById("varioBarSpacer").style.height = Vz1px+"px";
  document.getElementById("varioBar").style.height = Vz2px+"px";

  var temperature = Math.round(temperatureInit - 0.8*(me.pos[2]-me.takeOffAltitude)/100);
  document.getElementById("varioTemperature").innerHTML = "<font face='Comic Sans MS' color='#000000' size=5> "+Math.round(temperature)+"</font>";

  var heading = Math.round(180*Math.acos(deltaPos[1]/Math.pow(deltaPos[0]*deltaPos[0]+deltaPos[1]*deltaPos[1],0.5))/Math.PI);
  if (deltaPos[0] < 0) { heading = 360 - heading; }

  var wingHeading = me.model.getOrientation().getHeading();
  if (wingHeading < 0) { wingHeading = 360 + wingHeading; }
  var popo = 2*wingHeading + 110;
  //document.getElementById("compass_text").innerHTML = ""+wingHeading;
  document.getElementById("compass_image").style.right = popo+"px";
  document.getElementById("varioAltitude").innerHTML = "<font face='Comic Sans MS' color='#000000' size=5> "+Math.round(me.pos[2])+"</font>";
  document.getElementById("varioClimbingRate").innerHTML = "<font face='Comic Sans MS' color='#000000' size=4> "+Vz+"</font>";

  // GPS map center
  mapGPS.panTo(new GLatLng(lla[0], lla[1]));
  //  GPS info
  document.getElementById("GPSinfo").innerHTML = "<b>Speed : "+Math.round(3.6*10*airSpeed)/10+" km/h</b>"
  document.getElementById("GPSinfo").innerHTML += "<b><br />Heading : "+heading+"&ordm; </b>";
  rots.headingImg.rotateTo(heading);  

  // vario beep
  if (Vz >= 0){
    me.beepFrequency = 1+Vz;
  } else {
    me.beepFrequency = 0;
  }
 if (me.beepFrequency > 0 & varioSoundOnOff ){
   var deltaBeep = (now-me.lastBeep)/1000.0;  //when was last beep in seconds ?
   if (deltaBeep > 1/me.beepFrequency) {
     soundManager.play('beep');
     me.lastBeep = now;
   }
 }

  // text info when instruments are hidden
  document.getElementById("instrumentText").innerHTML = "<b>Vz : "+Vz+" m/s</b><br />";
  document.getElementById("instrumentText").innerHTML += "<b>Alt. : "+Math.round(me.pos[2])+" m</b><br />";
  document.getElementById("instrumentText").innerHTML += "<b>Gnd Speed : "+Math.round(3.6*airSpeed)+" km/h</b><br />"
  document.getElementById("instrumentText").innerHTML += "<b>Gnd Heading : "+heading+"&ordm; </b>";


  // Hack to work around focus bug
  // TODO: fix that bug and remove this hack.
  ge.getWindow().blur();
};



// Calculate local windSpeed vector
Truck.prototype.windLocal = function(lla) {
  var me = this;
  var windDirectionLocal = 90-WIND_DIRECTION ; // because x axis points east while wind direction is counted from north..
  var windDirectionRadians = windDirectionLocal * Math.PI / 180;
  //get glider altitude
  var groundAlt = ge.getGlobe().getGroundAltitude(lla[0], lla[1]);
  var altitude = me.pos[2]-groundAlt;
   //      document.getElementById("jsInfo2").innerHTML += '<br />AGL = '+Math.round(altitude)+" m";

  // let s orientate the wind parallel to the ground (up or down) if the glider is less than 200m AGL
  if (altitude<200){
     //get ground altitude 10 meters further than
     // the glider actual position in the direction of the wind
     // (to see if we are above a slope)
     var pointFurther = [me.pos[0]+10*Math.cos(windDirectionRadians),
                          me.pos[1]+10*Math.sin(windDirectionRadians),
                            0] ;
     gPointFurther = V3.add(me.localAnchorCartesian,
                M33.transform(me.localFrame, pointFurther));

     var llaPointFurther = V3.cartesianToLatLonAlt(gPointFurther);

     var pointFurtherAlt = ge.getGlobe().getGroundAltitude(llaPointFurther[0], llaPointFurther[1]);

     var groundAngle = Math.atan((groundAlt-pointFurtherAlt)/10);

     // if between 150 and 200 AGL : lets minize the ground deflection in a linear way
     // groundAngle varies from groundAngle at 150m to 0 at 200m
     if (altitude > 150) {
	groundAngle = (200-altitude) * groundAngle / 50;
     }

     // wind direction oriented parallel to the groundAngle
     me.wind = [-1*WIND_SPEED*Math.cos(windDirectionRadians)*Math.cos(groundAngle),
                     -1*WIND_SPEED*Math.sin(windDirectionRadians)*Math.cos(groundAngle),
                        WIND_SPEED*Math.sin(groundAngle)];
  } else {
    me.wind = [-WIND_SPEED*Math.cos(windDirectionRadians), -WIND_SPEED*Math.sin(windDirectionRadians), 0];
  }
};



// TODO: would be nice to have globe.getGroundNormal() in the API.
function estimateGroundNormal(pos, frame) {
  // Take four height samples around the given position, and use it to
  // estimate the ground normal at that position.
  //  (North)
  //     0
  //     *
  //  2* + *3
  //     *
  //     1
  var pos0 = V3.add(pos, frame[0]);
  var pos1 = V3.sub(pos, frame[0]);
  var pos2 = V3.add(pos, frame[1]);
  var pos3 = V3.sub(pos, frame[1]);
  var globe = ge.getGlobe();
  function getAlt(p) {
    var lla = V3.cartesianToLatLonAlt(p);
    return globe.getGroundAltitude(lla[0], lla[1]);
  }
  var dx = getAlt(pos1) - getAlt(pos0);
  var dy = getAlt(pos3) - getAlt(pos2);
  var normal = V3.normalize([dx, dy, 2]);
  return normal;
}

// Decide when to open & close popup messages.
Truck.prototype.tickPopups = function(dt) {
  var me = this;
  var speed = V3.length(me.vel);
  var height = 0;
  if (me.popupTimer > 0) {
    me.popupTimer -= dt;
    me.idleTimer = 0;
    me.fastTimer = 0;
    if (me.popupTimer <= 0) {
      me.popupTimer = 0;
      ge.setBalloon(null);
    }
  } else {
    if (speed < 20) {
      me.idleTimer += dt;
      if (me.idleTimer > 10.0) {
        me.showIdlePopup();
      }
      me.fastTimer = 0;
    } else {
      me.idleTimer = 0;
      if (speed > 80) {
        me.fastTimer += dt;
        if (me.fastTimer > 7.0) {
          me.showFastPopup();
        }
      } else {
        me.fastTimer = 0;
      }
    }
  }
};

var IDLE_MESSAGES = [
    "Let's fly !",
    "What about a pizza ?...",
    "I love fried chicken",
    "Wow, this is so beautiful",
    "Am I not a bit too high, here ?!",
    "Zzzzzzz",
    "Maybe i should change my glider..."
                     ];
Truck.prototype.showIdlePopup = function() {
  var me = this;
  me.popupTimer = 2.0;
  var rand = Math.random();
  var index = Math.floor(rand * IDLE_MESSAGES.length)
    % IDLE_MESSAGES.length;
  var message = "<center>" + IDLE_MESSAGES[index] + "</center>";
  me.balloon.setContentString(message);
  ge.setBalloon(me.balloon);
};

var FAST_MESSAGES = [
    "Whoah there, cowboy!",
    "Wheeeeeeeeee!",
    "<font size=+5 color=#8080FF>Creamy!</font>",
    "Hey, we're hauling glass bottles here!"
                     ];
Truck.prototype.showFastPopup = function() {
  var me = this;
  me.popupTimer = 2.0;
  var rand = Math.random();
  var index = Math.floor(rand * FAST_MESSAGES.length)
    % FAST_MESSAGES.length;
  var message = "<center>" + FAST_MESSAGES[index] + "</center>";
  me.balloon.setContentString(message);
  ge.setBalloon(me.balloon);
};

Truck.prototype.scheduleTick = function() {
  var me = this;
  if (me.doTick) {
    setTimeout(function() { me.tick(); }, TICK_MS);
  }
};


// Cut the camera to look at me.
Truck.prototype.cameraCut = function() {
  var me = this;
  var lo = me.model.getLocation();
  var la = ge.createLookAt('');
  la.set(lo.getLatitude(), lo.getLongitude(),
         10 /* altitude */,
         ge.ALTITUDE_RELATIVE_TO_GROUND,
         fixAngle(180 + me.model.getOrientation().getHeading() + 45),
         80, /* tilt */
         50 /* range */         
         );
  ge.getView().setAbstractView(la);
};

Truck.prototype.cameraFollow = function(dt, truckPos, localToGlobalFrame, headHeading, headTilt) {
  var me = this;

  var c0 = Math.exp(-dt / 0.5);
  var c1 = 1 - c0;

  var la = ge.getView().copyAsLookAt(ge.ALTITUDE_RELATIVE_TO_GROUND);

  var truckHeading = me.model.getOrientation().getHeading();
  var camHeading = la.getHeading();
  var camTilt    = la.getTilt();

  var deltaHeading = fixAngle(truckHeading + headHeading - camHeading);
  var heading = camHeading + c1 * deltaHeading;
  heading = fixAngle(heading);


  var headingRadians = heading / 180 * Math.PI;

  var deltaTilt = fixAngle(headTilt - camTilt);
  var tilt = camTilt + c1 * deltaTilt;

 if (cameraZoomIn){
    me.camZoom = clamp(0.90 * me.camZoom, 0.2, 20);
  }
  if (cameraZoomOut){
    me.camZoom = clamp(1.1 * me.camZoom, 0.2, 20);
  } 
  
  var TRAILING_DISTANCE = 90 * me.camZoom * Math.sin ( tilt / 180 * Math.PI ) ;
  var CAM_HEIGHT = 90 * me.camZoom * Math.cos ( tilt / 180 * Math.PI ) ;

  var headingDir = V3.rotate(localToGlobalFrame[1], localToGlobalFrame[2],
                             -headingRadians);
  var camPos = V3.add(truckPos, V3.scale(localToGlobalFrame[2], CAM_HEIGHT));
  camPos = V3.add(camPos, V3.scale(headingDir, -TRAILING_DISTANCE));
  var camLla = V3.cartesianToLatLonAlt(camPos);
  var camLat = camLla[0];
  var camLon = camLla[1];
  var camAlt = camLla[2] - ge.getGlobe().getGroundAltitude(camLat, camLon);

  la.set(camLat, camLon, camAlt, ge.ALTITUDE_RELATIVE_TO_GROUND, 
        heading, tilt /*tilt*/, 0 /*range*/);
  ge.getView().setAbstractView(la);
};

// heading and cheatAltitudeGain are optional.
Truck.prototype.teleportTo = function(lat, lon, heading, cheatAltitudeGain) {

  var headingRadians = heading / 180 * Math.PI;
  var me = this;
  if (heading == null) {
    heading = 0;
  }
  if (cheatAltitudeGain == null) {
    cheatAltitudeGain = 0;
  }

  me.model.getLocation().setLatitude(lat);
  me.model.getLocation().setLongitude(lon);
  me.model.getLocation().setAltitude(ge.getGlobe().getGroundAltitude(lat, lon) + WINCH_ALTITUDE + cheatAltitudeGain);
  //me.vel = [10*Math.cos(headingRadians), 10*Math.sin(headingRadians), -1.3];
  //me.vel=[10,0,0];
  me.vel=[0,0,0];

  me.localAnchorLla = [lat, lon, 0];
  me.localAnchorCartesian = V3.latLonAltToCartesian(me.localAnchorLla);
  me.localFrame = M33.makeLocalToGlobalFrame(me.localAnchorLla);
  me.modelFrame = M33.identity();
  me.modelFrame[0] = V3.rotate(me.modelFrame[0], me.modelFrame[2], -headingRadians);
  me.modelFrame[1] = V3.rotate(me.modelFrame[1], me.modelFrame[2], -headingRadians);
  me.pos = [0, 0, ge.getGlobe().getGroundAltitude(lat, lon) + 10 + WINCH_ALTITUDE + cheatAltitudeGain];

  me.cameraCut();
};

// Move our anchor closer to our current position.  Retain our global
// motion state (position, orientation, velocity).
Truck.prototype.adjustAnchor = function() {
  var me = this;
  var oldLocalFrame = me.localFrame;

  var globalPos = V3.add(me.localAnchorCartesian,
                         M33.transform(oldLocalFrame, me.pos));
  var newAnchorLla = V3.cartesianToLatLonAlt(globalPos);
  newAnchorLla[2] = 0;  // For convenience, anchor always has 0 altitude.

  var newAnchorCartesian = V3.latLonAltToCartesian(newAnchorLla);
  var newLocalFrame = M33.makeLocalToGlobalFrame(newAnchorLla);

  var oldFrameToNewFrame = M33.transpose(newLocalFrame);
  oldFrameToNewFrame = M33.multiply(oldFrameToNewFrame, oldLocalFrame);

  var newVelocity = M33.transform(oldFrameToNewFrame, me.vel);
  var newModelFrame = M33.multiply(oldFrameToNewFrame, me.modelFrame);
  var newPosition = M33.transformByTranspose(
      newLocalFrame,
      V3.sub(globalPos, newAnchorCartesian));

  me.localAnchorLla = newAnchorLla;
  me.localAnchorCartesian = newAnchorCartesian;
  me.localFrame = newLocalFrame;
  me.modelFrame = newModelFrame;
  me.pos = newPosition;
  me.vel = newVelocity;
}

// Keep an angle in [-180,180]
function fixAngle(a) {
  while (a < -180) {
    a += 360;
  }
  while (a > 180) {
    a -= 360;
  }
  return a;
}

Truck.prototype.endFlight = function(){
  var me = this;
 
    //********** IGC Last Point at Landing
    if (!me.StopWrittingIGCOnceForAll){
    var dateNow = new Date();
    var latText='N';
    var lngText='E';
    var lat = me.lla[0];
    var lng = me.lla[1];
    if (me.lla[0]<0) {latText='S'; lat = - lat} 
    if (me.lla[1]<0) {lngText='W'; lng = - lng}
    var lat = addExtraZeros(2, Math.floor(lat))+''+ addExtraZeros(5, Math.round( 60 * Math.round(100000 * (lat - Math.floor(lat))) / 100)); // convert decimal degrees to decimal minutes
    var lng = addExtraZeros(3, Math.floor(lng))+''+ addExtraZeros(5, Math.round( 60 * Math.round(100000 * (lng - Math.floor(lng))) / 100)); // convert decimal degrees to decimal minutes
    me.igc += 'B'+ addExtraZeros(2,dateNow.getHours()) +''+addExtraZeros(2,dateNow.getMinutes())+''+addExtraZeros(2,dateNow.getSeconds());
    me.igc += lat+''+latText;
    me.igc += lng+''+lngText;
    me.igc += 'A'+addExtraZeros(10, Math.round(me.lla[2]))+'\n';
    me.igc +='LXGD Downloaded 2006-05-01  16:40:12';
    }
    //********** End of IGC Last Point at Landing


  
  me.StopWrittingIGCOnceForAll = true;

  document.getElementById("map3d").style.display = "none";

  document.getElementById("endFlight").innerHTML = "<center>Wasn't that a good flight !?!";
  document.getElementById("endFlight").innerHTML += "<br />Biggest distance from your take-off was "+Math.round(me.maxDistanceFromTakeOff)+" m";
  document.getElementById("endFlight").innerHTML += "<br />Highest altitude was "+Math.round(me.maxAltitude)+" m";
  document.getElementById("endFlight").innerHTML += '<br />&nbsp;<br /><input type=button class="button medw" onmousedown="javascript:window.location.reload(true);" value="Hitch hike back to takeoff for another flight">'
  document.getElementById("endFlight").innerHTML += "</center>";
  document.getElementById("pilotButtons").style.display = "none";
  document.igcForm.igcFile.value = me.igc;
  document.getElementById("igc").style.display = "block";
  
  document.getElementById("vario").style.display = "none";
  document.getElementById("GPS").style.display = "none";
  document.getElementById("pilotButtons").style.display = "none";
  document.getElementById("compass_container").style.display = "none";
  
  truck = null;
  ge = null;
}

Truck.prototype.setUpThermals = function(latLngAlt){
  var me = this;
  //initialize this pos
  me.lastThermalSetUpPos = latLngAlt;
  //choose "THERMAL_DENSITY" random points in a 1000 m side square ( +/- 500 meters)
  for (var i=0; i < THERMAL_DENSITY; i++){
    var randX = Math.floor(Math.random() * 1000)-500;
    var randY = Math.floor(Math.random() * 1000)-500;
    me.thermalCenter[i] = [randX, randY, 0];
    me.thermalCenterLla[i] = V3.cartesianToLatLonAlt(V3.add(me.localAnchorCartesian, me.thermalCenter[i]));
  }
}

Truck.prototype.thermalCenterWithWindOffsetLla = function(thermal){
  var me = this;
   // document.getElementById("jsInfo3").innerHTML = thermal+'--<br/>';
    var windOffsetCartesian = [me.lla[2] * me.wind[0] / THERMAL_VZ_MAX, me.lla[2] * me.wind[1] / THERMAL_VZ_MAX, 0];
    var LngRadius = 6400000 * Math.cos(Math.PI * thermal[0] / 180) ;  //earth 'longitudinal' radius at this point
    var windOffsetLla = [windOffsetCartesian[1]*90/10000000, windOffsetCartesian[1]*180/(LngRadius/2), 0] ;
    var thermalNewPos = V3.add(thermal,windOffsetLla);
    return  thermalNewPos ;
}

Truck.prototype.inThermal = function(){
  var me = this;
//  document.getElementById("jsInfo").innerHTML += '<br /><b>IN THERMAL !!!!</b>' ;
  me.flyingInThermal = 1;
  var distanceToCenter = getDistance(me.lla, me.myActualThermalLla);
//  document.getElementById("jsInfo").innerHTML += '<br />distance to center : '+distanceToCenter+" m";
  if(distanceToCenter < 0.6 * THERMAL_RADIUS){
    me.thermalUpliftSpeed = (0.7-1) * THERMAL_VZ_MAX * distanceToCenter / (0.6 * THERMAL_RADIUS) + THERMAL_VZ_MAX ;
  } else {
    me.thermalUpliftSpeed = ( (0.7-(-0.2)) * THERMAL_VZ_MAX * distanceToCenter / ((0.6-1) * THERMAL_RADIUS ))
                                + (-0.2-((0.7-(-0.2))/(0.6-1))) * THERMAL_VZ_MAX ;     //linear uplift with distance to center (with 'downlift' in border of thermal)
  }

  if (distanceToCenter > THERMAL_RADIUS ||  me.lla[2]>THERMAL_TOP){
    me.flyingInThermal = 0 ;
  }
}

function getDistance(lla1, lla2){
 var R = 6371000; // earth radius in meters
 var lat1 = lla1[0]*Math.PI/180;
 var lon1 = lla1[1]*Math.PI/180;
 var lat2 = lla2[0]*Math.PI/180;
 var lon2 = lla2[1]*Math.PI/180;
 var d = Math.acos(Math.sin(lat1)*Math.sin(lat2) +
                  Math.cos(lat1)*Math.cos(lat2) *
                  Math.cos(lon2-lon1)) * R;
 return Math.round(d);
}

function addExtraZeros(size, number){
  var temp = ''+number; //convert number to string
  while (temp.length < size){
    temp = '0'+temp;
  }
  return temp;
}
